lesson-2_Build Frontend
Create a web frontend page
🛠 Generate Project
The terminal panel, located at the bottom of the ChainIDE screen, will be manipulated.
First, select "Sandbox" and click "Click to add Sandbox +".
Make sure Sandbox is up and running.
Make sure you have node
18.17.0 or newer.
node -v
If you don't, install the stable version and check the newly activated version.
# Install n to manage your Node.js versions
npm install -g n
# Install and activate the stable version of Node.js
n stable
# Confirm Node.js version 18.17.0 or newer
node -v
Make sure you are in the root directory of the project and execute the following command.
yarn create next-app
Let's name the project client
and create the project by selecting the following options.
✔ What is your project named? … client
✔ Would you like to use TypeScript? … [Yes]
✔ Would you like to use ESLint? … [Yes]
✔ Would you like to use Tailwind CSS? … [No]
✔ Would you like to use `src/` directory? … [No]
✔ Would you like to use App Router? (recommended) … [No]
✔ Would you like to customize the default import alias? … [No]
Make sure that client/
has been created.
Go to the client folder and install the necessary packages.
cd client
yarn add @metamask/providers@^13.0.0 ethers@^5
Clean up your files.
Delete pages/api/
.
Now, let's update the files.
Let's overwrite styles/global.css
with the following code.
html,
body {
width: 100%;
height: 100%;
}
body {
display: flex;
align-items: center;
justify-content: center;
}
Overwrite styles/Home.module.css
with the following code.
.container {
width: 100%;
max-width: 500px;
}
.container label {
font-size: 1.5rem;
margin-right: 0.5rem;
}
.container input {
width: 70%;
font-size: 1.25rem;
padding: 10px;
border-radius: 5px;
}
.container button {
width: 100%;
font-size: 1.25rem;
padding: 12px 20px;
margin-right: 0.5rem;
border-radius: 5px;
}
.container button:hover {
background-color: #ddd;
}
Create @types/global.d.ts
in the client folder.
Write the following code.
import { MetaMaskInpageProvider } from "@metamask/providers";
declare global {
interface Window {
ethereum: MetaMaskInpageProvider;
}
}
🚗 Generate Code
Paste the complete code into pages/index.tsx
in the client folder.
import Head from 'next/head';
import { Inter } from 'next/font/google';
import styles from '@/styles/Home.module.css';
import { useState } from 'react';
import { ethers, ContractTransaction } from 'ethers';
const inter = Inter({ subsets: ['latin'] });
const contractAddress = "";
const abi = ;
export default function Home() {
const [amount] = useState('0.01');
const [connectStatus, setConnectStatus] = useState('connect');
const connect = async () => {
if (typeof window.ethereum !== 'undefined') {
try {
await window.ethereum.request({
method: 'eth_requestAccounts',
});
} catch (error) {
console.log(error);
}
await switchToMumbai();
setConnectStatus('connected');
} else {
setConnectStatus('Please install MetaMask');
}
};
const mint = async () => {
console.log(`Mint...`);
if (typeof window.ethereum !== 'undefined') {
// @ts-expect-error: ethereum as ethers.providers.ExternalProvider
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(
contractAddress,
abi,
signer,
);
try {
const transactionResponse = await contract.mint({
value: ethers.utils.parseEther(amount),
});
await waitForTransaction(transactionResponse, provider);
console.log('Mint succeed!');
} catch (error) {
console.log(error);
}
} else {
setConnectStatus('Please install MetaMask');
}
};
const withdraw = async () => {
console.log(`Withdraw...`);
if (typeof window.ethereum !== 'undefined') {
// @ts-expect-error: ethereum as ethers.providers.ExternalProvider
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(
contractAddress,
abi,
signer,
);
try {
const transactionResponse = await contract.withdraw();
await waitForTransaction(transactionResponse, provider);
console.log('withdraw succeed!');
} catch (error) {
console.log(error);
}
} else {
setConnectStatus('Please install MetaMask');
}
};
const switchToMumbai = async () => {
const chainId = '0x13882'; // Amoy
const currentChainId = await window.ethereum.request({
method: 'eth_chainId',
});
if (currentChainId !== chainId) {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [
{
chainId: chainId,
},
],
});
/* eslint-disable @typescript-eslint/no-explicit-any */
} catch (err: any) {
// This error code indicates that the chain has not been added to MetaMask
if (err.code === 4902) {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [
{
chainName: 'Amoy',
chainId: chainId,
nativeCurrency: {
name: 'MATIC',
decimals: 18,
symbol: 'MATIC',
},
rpcUrls: [
'https://endpoints.omniatech.io/v1/matic/amoy/public',
],
},
],
});
}
}
}
};
const waitForTransaction = async (
transactionResponse: ContractTransaction,
provider: ethers.providers.Web3Provider,
) => {
console.log(`Mining ${transactionResponse.hash}`);
return new Promise<void>((resolve, reject) => {
try {
provider.once(transactionResponse.hash, (transactionReceipt) => {
console.log(
`Completed with ${transactionReceipt.confirmations} confirmations. `,
);
resolve();
});
} catch (error) {
reject(error);
}
});
};
return (
<>
<Head>
<title>mint demo</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={inter.className}>
<div className={styles.container}>
<div>
<label htmlFor="value">value:</label>
<input id="value" value={amount} readOnly />
</div>
<div>
<button id="connectButton" onClick={connect}>
{connectStatus}
</button>
<button id="mintButton" onClick={mint}>
mint
</button>
<button id="withdrawButton" onClick={withdraw}>
withdraw
</button>
</div>
</div>
</main>
</>
);
}
Don't rush, we still need to fill in some information.
const contractAddress = "";